package cn.yangliu.utils;

import cn.yangliu.PayConst;
import cn.yangliu.entity.PayEntity;
import cn.yangliu.entity.PayOrderInfo;
import cn.yangliu.entity.WXNotifyInfo;
import cn.yangliu.entity.ZFBNotifyInfo;
import cn.yangliu.enums.PayStatusEnum;
import cn.yangliu.utils.wexin.CollectionUtil;
import cn.yangliu.utils.wexin.HttpUtils;
import cn.yangliu.utils.wexin.PayUtil;
import cn.yangliu.utils.wexin.XmlUtil;
import cn.yangliu.work.*;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class PayUtils {

    private static final Logger logger = LoggerFactory.getLogger(PayUtils.class);

    private static final String WX_ORDER_PAY = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 统一下单
    private static AlipayClient client;

    static {
        init();
    }

    public static void init() {
        if (client == null) {
            client = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
                    PayConst.ZFB_APPID,
                    PayConst.ZFB_PRIVATE_KEY,
                    "json",
                    "UTF-8",
                    PayConst.ZFB_PUBLIC_KEY,
                    "RSA2"
            );
        }
    }

    public static Object createOrderInfo(HttpServletRequest request, PayEntity payEntity, PayBeforeWorker... workers) {
        String payType = request.getParameter("payType");
        if (StringUtils.isEmpty(payType)) {
            throw new NullPointerException("缺少参数payType");
        }

        switch (payType) {
            case "wechat":
                return wxCreateOrderInfo(request, payEntity, workers);
            case "alipay":
                return aliCreateOrderInfo(request, payEntity, workers);
            default:
                throw new RuntimeException("不支持的支付方式");
        }
    }

    protected static Map<String, String> wxCreateOrderInfo(HttpServletRequest request, PayEntity payInfo, PayBeforeWorker... workers) {

        Map<String, String> restmap = null;
        String total_fee = payInfo.getAmount().multiply(BigDecimal.valueOf(100))
                .setScale(0, BigDecimal.ROUND_HALF_UP).toString();
        String orderNo = OrderUtils.createOrderNo();
        PayOrderInfo order = OrderUtils.createOrderInfo(payInfo, "wechat", orderNo);
        PayBeforeWorkHandler handler = new PayBeforeWorkHandler(order, request);
        handler.doWork(workers);
        Map<String, String> parm = new HashMap<>();
        parm.put("appid", PayConst.WX_APP_ID);
        parm.put("mch_id", PayConst.WX_MCH_ID);
        parm.put("device_info", "WEB");
        parm.put("nonce_str", PayUtil.getNonceStr());
        parm.put("body", payInfo.getDesp());
        parm.put("attach", "Andy");
        parm.put("out_trade_no", orderNo);
        parm.put("total_fee", total_fee);
        parm.put("spbill_create_ip", PayUtil.getRemoteAddrIp(request));
        parm.put("notify_url", PayConst.NOTIFY_PREFFIX + PayConst.WX_NOTIFY_URL); //微信服务器异步通知支付结果地址  下面的order/notify 方法
        parm.put("trade_type", "APP");
        parm.put("sign", PayUtil.getSign(parm, PayConst.WX_API_SECRET));
        String s = XmlUtil.xmlFormat(parm, false);
        String restxml = HttpUtils.post(WX_ORDER_PAY, s);
        restmap = XmlUtil.xmlParse(restxml);
        Map<String, String> payMap = new HashMap<>();
        if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
            payMap.put("appid", PayConst.WX_APP_ID);
            payMap.put("partnerid", PayConst.WX_MCH_ID);
            payMap.put("prepayid", restmap.get("prepay_id"));
            payMap.put("package", "Sign=WXPay");
            payMap.put("noncestr", PayUtil.getNonceStr());
            payMap.put("timestamp", PayUtil.payTimestamp());
            payMap.put("sign", PayUtil.getSign(payMap, PayConst.WX_API_SECRET));
            payMap.put("bizOrderNo", orderNo);

        }
        return payMap;

    }

    /**
     * 支付宝创建支付订单
     *
     * @return
     */
    protected static String aliCreateOrderInfo(HttpServletRequest request, PayEntity payEntity, PayBeforeWorker... workers) {
        String orderNo = OrderUtils.createOrderNo();
        PayOrderInfo order = OrderUtils.createOrderInfo(payEntity, "aliPay", orderNo);
        PayBeforeWorkHandler handler = new PayBeforeWorkHandler(order, request);
        handler.doWork(workers);


        AlipayTradeAppPayRequest alipayRequest = new AlipayTradeAppPayRequest();//创建API对应的request

        alipayRequest.setNotifyUrl(PayConst.NOTIFY_PREFFIX + PayConst.ALI_NOTIFY_URL);//在公共参数中设置回跳和通知地址
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setBody(payEntity.getAmount().setScale(2, BigDecimal.ROUND_DOWN).toString() + "元");
        model.setSubject(payEntity.getGoods());
        model.setOutTradeNo(orderNo);
        model.setTimeoutExpress("30m");
        model.setTotalAmount(payEntity.getAmount().setScale(2, BigDecimal.ROUND_DOWN).toString());
        model.setProductCode("QUICK_MSECURITY_PAY");
        alipayRequest.setBizModel(model);

        AlipayTradeAppPayResponse alipayTradeAppPayResponse = null;
        try {
            alipayTradeAppPayResponse = client.sdkExecute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error(e.getMessage(), e);
        }
        //response.getBody()就是orderString 可以直接给客户端请求，无需再做处理。
        return alipayTradeAppPayResponse.getBody();
    }

    public static Map<String,String> getParamsMap(HttpServletRequest request){
        Map<String,String> params = new HashMap<>();
        Map<String,String[]> requestParams = request.getParameterMap();
        for (Map.Entry<String, String[]> stringEntry : requestParams.entrySet()) {
            params.put(stringEntry.getKey(),request.getParameter(stringEntry.getKey()));
        }
        return params;
    }

    public static void aliNotify(HttpServletRequest request, UpdateWorker updateWorker, UpdateErrorCallBack errorCallBack, BusinessWorker... workers) {
        Map<String,String> params = getParamsMap(request);
        try{
            if (!AlipaySignature.rsaCheckV2(params,PayConst.ZFB_PUBLIC_KEY,"UTF-8")){
                throw new RuntimeException("不合法的异步通知");
            }
        }catch (Exception e){
            logger.error(e.getMessage(),e);
            throw new RuntimeException("验证签名异常");
        }

        String out_trade_no = request.getParameter("out_trade_no");
        String subject = request.getParameter("subject");
        String trade_no = request.getParameter("trade_no");
        String trade_status = request.getParameter("trade_status");
        String seller_id = request.getParameter("seller_id");
        String total_fee = request.getParameter("total_fee");
        String body = request.getParameter("body");
        Date notify_time = null;
        if (StringUtils.isNotEmpty(request.getParameter("notify_time"))) {
            notify_time = DateUtils.string2Date(request.getParameter("notify_time"), DateUtils.DATEANDTIME);
        }
        Date gmt_create = null;
        if (StringUtils.isNotEmpty(request.getParameter("gmt_create"))) {
            gmt_create = DateUtils.string2Date(request.getParameter("gmt_create"), DateUtils.DATEANDTIME);
        }
        //交易创建时间
        Date gmt_payment = null; //交易付款时间
        if (StringUtils.isNotEmpty(request.getParameter("gmt_payment"))) {
            gmt_payment = DateUtils.string2Date(request.getParameter("gmt_payment"), DateUtils.DATEANDTIME);
        }
        ZFBNotifyInfo notifyInfo = new ZFBNotifyInfo(notify_time, out_trade_no, subject,
                trade_no, trade_status, seller_id, total_fee, body, gmt_create, gmt_create);
        PayStatusEnum statusEnum = null;
        switch (notifyInfo.getTrade_status()) {

            case "TRADE_SUCCESS":// 	支付成功 	true（触发通知）
                statusEnum = PayStatusEnum.SUCCESS;
                break;
            case "TRADE_CLOSED":// 	交易关闭 	true（触发通知）
                statusEnum = PayStatusEnum.CLOSE;
                break;
            case "TRADE_FINISHED":// 	交易完成 	true（触发通知）
                statusEnum = PayStatusEnum.FINISHED;
                break;
        }

        BigDecimal payAmount = null;
        String buyer_pay_amount = request.getParameter("buyer_pay_amount");
        if (StringUtils.isNotEmpty(buyer_pay_amount)) {
            payAmount = new BigDecimal(buyer_pay_amount);
        }

        NotifyWorkHandler handler = new NotifyWorkHandler(out_trade_no, trade_no, payAmount, statusEnum, gmt_payment);
        handler.doWork(updateWorker, errorCallBack, workers);
    }

    public static void wxNotify(HttpServletResponse response, HttpServletRequest request, UpdateWorker updateWorker, UpdateErrorCallBack errorCallBack, BusinessWorker... workers) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/xml");
        PrintWriter out = null;
        Map<String, String> retMap = new HashMap<>();
        retMap.put("return_code", "FAIL");
        try {
            out = response.getWriter();

            ServletInputStream in = request.getInputStream();
            String resxml = readInputStream2String(in);
            Map<String, String> restmap = XmlUtil.xmlParse(resxml);
            logger.info("支付结果通知：" + restmap);
            if ("SUCCESS".equals(restmap.get("return_code"))) {
                // 订单支付成功 业务处理
                String out_trade_no = restmap.get("out_trade_no"); // 商户订单号
                // 通过商户订单判断是否该订单已经处理 如果处理跳过 如果未处理先校验sign签名 再进行订单业务相关的处理

                String sign = restmap.get("sign"); // 返回的签名
                restmap.remove("sign");
                String signnow = PayUtil.getSign(restmap, PayConst.WX_API_SECRET);

                String json = JSON.toJSONString(restmap);
                WXNotifyInfo notifyInfo = JSON.parseObject(json, WXNotifyInfo.class);

                PayStatusEnum statusEnum = null;
                switch (notifyInfo.getReturn_code()) {
                    case "FAIL":// 	失败
                        statusEnum = PayStatusEnum.FAIL;
                        break;
                    case "SUCCESS":// 	支付成功
                        statusEnum = PayStatusEnum.SUCCESS;
                        break;

                }
                BigDecimal payAmount = null;
                NotifyWorkHandler handler = new NotifyWorkHandler(out_trade_no, notifyInfo.getTransaction_id(),payAmount, statusEnum, new Date());
                handler.doWork(updateWorker, errorCallBack, workers);

                if (signnow.equals(sign)) {
                    // 进行业务处理
                    logger.info("订单支付通知： 支付成功，订单号" + out_trade_no);
                    retMap.put("return_code", "SUCCESS");
                } else {
                    logger.info("订单支付通知：签名错误");
                }
            } else {
                logger.info("订单支付通知：支付失败，" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            out.print(XmlUtil.xmlFormat(retMap, false));
            out.flush();
        }
    }


    private static final String readInputStream2String(InputStream in) throws IOException {
        if (in == null)
            return "";
        StringBuffer out = new StringBuffer();
        byte[] b = new byte[1024];
        for (int n; (n = in.read(b)) != -1; ) {
            out.append(new String(b, 0, n, "UTF-8"));
        }
        return out.toString();
    }

    public static final PayEntity createPayEntity(HttpServletRequest request) {
        if (StringUtils.isEmpty(request.getParameter("amount"))) {
            throw new NullPointerException("缺少参数amount");
        }
        if (StringUtils.isEmpty(request.getParameter("price"))) {
            throw new NullPointerException("缺少参数price");
        }
        if (StringUtils.isEmpty(request.getParameter("desp"))) {
            throw new NullPointerException("缺少参数desp");
        }
        if (StringUtils.isEmpty(request.getParameter("goods"))) {
            throw new NullPointerException("缺少参数goods");
        }
        if (StringUtils.isEmpty(request.getParameter("payType"))) {
            throw new NullPointerException("缺少参数payType");
        }
        if (StringUtils.isEmpty(request.getParameter("count"))) {
            throw new NullPointerException("缺少参数count");
        }

        BigDecimal amount = new BigDecimal(request.getParameter("amount"));
        BigDecimal price = new BigDecimal(request.getParameter("price"));
        String payType = request.getParameter("payType");
        String desp = request.getParameter("desp");
        String goods = request.getParameter("goods");
        int count = Integer.parseInt(request.getParameter("count"));

        return new PayEntity(amount, payType, desp, goods, price, count);

    }

    public static boolean refund(PayOrderInfo orderInfo, String reason) {
        if ("alipay".equalsIgnoreCase(orderInfo.getPayType())) {
            return aliRefund(orderInfo, reason);
        }

        return false;
    }

    private static boolean aliRefund(PayOrderInfo orderInfo, String reason) {
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
        model.setTradeNo(orderInfo.getPayOrderNo());
        model.setOutTradeNo(orderInfo.getOrderNo());
        model.setRefundAmount(orderInfo.getAmount().setScale(2, BigDecimal.ROUND_DOWN).toString());
        model.setRefundReason(reason);
        request.setBizModel(model);
        AlipayTradeRefundResponse response;
        try {
            response = client.execute(request);
            return response.isSuccess();
        } catch (AlipayApiException e) {
            logger.error(e.getMessage(), e);
            return false;
        }
    }

}
